package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.course.CatalogueClient;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CataSimpleInfoDTO;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.course.CourseSearchDTO;
import com.tianji.api.dto.course.CourseSimpleInfoDTO;
import com.tianji.api.dto.trade.OrderBasicDTO;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.domain.query.PageQuery;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningPlanDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.domain.vo.LearningLessonVO;
import com.tianji.learning.domain.vo.LearningPlanPageVO;
import com.tianji.learning.domain.vo.LearningPlanVO;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.PlanStatus;
import com.tianji.learning.mapper.LearningLessonMapper;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 学生课表 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2024-03-11
 */
@Service
@SuppressWarnings("ALL")
@RequiredArgsConstructor/*(onConstructor_= {@Lazy})*/ // 自动注入构造函数 只自动注入用final修饰的关键字
@Slf4j
@Lazy
public class LearningLessonServiceImpl extends ServiceImpl<LearningLessonMapper, LearningLesson> implements ILearningLessonService {

    private final CourseClient courseClient;
    private final CatalogueClient catalogueClient;
    private final LearningRecordMapper recordMapper;

    /**
     * 添加课程到我的课表
     *
     * @param userId
     * @param courseIds
     */
    @Override
    @Transactional
    public void addUserLessons(Long userId, List<Long> courseIds) {
        //1.查询有无该课程
        List<CourseSimpleInfoDTO> cInfoList = courseClient.getSimpleInfoList(courseIds);
        if (CollUtils.isEmpty(cInfoList)) {
            // 课程不存在 无法添加
            log.error("课程信息不存在，无法添加到课表");
            return;
        }
        // 遍历 cInfoList 处理LearingLesson数据
        List<LearningLesson> list = new ArrayList<>(cInfoList.size());
        for (CourseSimpleInfoDTO cInfo : cInfoList) {
            LearningLesson learning = new LearningLesson();
            // 添加过期时间
            Integer validDuration = cInfo.getValidDuration();
            if (validDuration != null && validDuration > 0) {
                LocalDateTime now = LocalDateTime.now();
                // 添加课程到期时间
                learning.setExpireTime(now.plusMinutes(validDuration));
            }
            // 添加用户购买课时时间
            learning.setCreateTime(LocalDateTime.now());
            //2.2添加UserId和CourseId
            learning.setUserId(userId);
            learning.setCourseId(cInfo.getId());
            // 添加到集合中
            list.add(learning);
        }
        //3.新增批量添加
        saveBatch(list);
    }

    /**
     * 我的课表分页查询排序字段
     *
     * @param query
     * @return
     */
    @Override
    public PageDTO<LearningLessonVO> queryMyLessons(PageQuery query) {
        //1.获得当前用户ID
        Long userId = UserContext.getUser();
        //2.分页查询
        // select * from learning_lessons where userId={userId} order by latest_learn_time limit 0,5
        Page<LearningLesson> page = lambdaQuery().eq(LearningLesson::getUserId, userId) //here userId={userId}
                .page(query.toMpPage("latest_learn_time", false));
        List<LearningLesson> records = page.getRecords();
        // 健壮性判断
        if (CollUtils.isEmpty(records)) {
            return PageDTO.empty(page);
        }
        //3.查询课程信息
        //3.1 获得课程id
        Set<Long> longs = records.stream().map(LearningLesson::getCourseId).collect(Collectors.toSet());
        //3.2 查询课程信息
        List<CourseSimpleInfoDTO> infoList = courseClient.getSimpleInfoList(longs);
        // 健壮性判断
        if (CollUtils.isEmpty(infoList)) {
            throw new BadRequestException("课程信息不存在！！");
        }
        //3.3 将records转换成map集合 key是课程id value是 LearningLesson集合
        Map<Long, CourseSimpleInfoDTO> cmap =
                infoList.stream().collect(Collectors.toMap(CourseSimpleInfoDTO::getId, c -> c));
        //4.封装VO返回
        List<LearningLessonVO> list = new ArrayList<>(infoList.size());
        //4.1 遍历 records<LearningLesson>
        for (LearningLesson r : records) {
            // 拷贝对象到vo
            LearningLessonVO vo = BeanUtils.copyBean(r, LearningLessonVO.class);
            // 根据设定的key获得对应的value值
            CourseSimpleInfoDTO infoDTO = cmap.get(r.getCourseId());
            vo.setCourseName(infoDTO.getName());
            vo.setCourseCoverUrl(infoDTO.getCoverUrl());
            vo.setSections(infoDTO.getSectionNum());
            // 添加到集合
            list.add(vo);
        }
        return PageDTO.of(page, list);
    }

    /**
     * 查询最近学习的课程
     *
     * @return
     */
    @Override
    public LearningLessonVO queryMyCurrentLesson() {
        // 1.根据用户id查询用户课程的基本信息
        Long userId = UserContext.getUser();
        //1.2 查询用户的课表信息
        // 查询正在学习的课程 select * from where userId={userId},status={status}
        LearningLesson lesson = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getStatus, LessonStatus.LEARNING.getValue())
                .orderByDesc(LearningLesson::getLatestLearnTime)
                .last("limit 1")
                .one();
        if (lesson == null) {
            return null;
        }
        // 2.拷贝到vo里 根据vo里缺少的信息 再做查询补充
        LearningLessonVO vo = BeanUtils.copyBean(lesson, LearningLessonVO.class);
        // 3.查询课程信息
        CourseSearchDTO courseInfoById = courseClient.getSearchInfo(lesson.getCourseId());
        if (courseInfoById == null) {
            throw new BadRequestException("课程不存在");
        }
        vo.setCourseName(courseInfoById.getName());
        vo.setSections(courseInfoById.getSections());
        vo.setCourseCoverUrl(courseInfoById.getCoverUrl());
        // 4.统计课程的总学习数
        // select count(1) from xxx userid={userid}
        Integer courseAmount = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .count();
        vo.setCourseAmount(courseAmount);
        // 5.根据小结Id 查询小结信息
        List<CataSimpleInfoDTO> cataSimpleInfoDTOS =
                catalogueClient.batchQueryCatalogue(CollUtils.singletonList(lesson.getCourseId()));
        if (cataSimpleInfoDTOS == null) {
            throw new BadRequestException("课程小结信息不存在");
        }
        CataSimpleInfoDTO cataSimpleInfoDTO = cataSimpleInfoDTOS.get(0);
        vo.setLatestSectionName(cataSimpleInfoDTO.getName());
        return vo;
    }

    /**
     * 删除课表
     *
     * @param userId
     * @param courseIds
     */
    @Override
    @Transactional
    //
    public void deleteLesson(Long courseId) {
        Long userId = UserContext.getUser();
        // 查询用户要删除的课表信息
        LearningLesson learningLesson = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        // 健壮性判断
        if (learningLesson==null) {
            throw new BadRequestException("删除的课程不存在");
        }
        if (learningLesson.getStatus()!=LessonStatus.EXPIRED){
            throw new BadRequestException("未过期的课程不能删除");
        }
        // 删除课程
        removeById(learningLesson.getId());
    }

    /**
     * 检查课程是否有效
     *
     * @param courseId
     * @return
     */
    @Override
    public Long isLessonValid(Long courseId) {
        //1.获得当前用户ID
        Long userId = UserContext.getUser();
        //2.查询当前用户对应课程的状态
        LearningLesson lesson = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId).one();
        // 健壮性判断
        if (lesson == null) {
            throw new BadRequestException("课表里的课程不存在");
        }
        //3.判断是否有效 无效返回null
        if (lesson.getStatus() == LessonStatus.EXPIRED) {
            return null;
        }
        return lesson.getId();
    }

    /**
     * 查看用户指定课程状态
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLesson getLessonStatus(Long courseId) {
        //1.获得当前用户的Id
        Long userId = UserContext.getUser();
        //2.根据课程id获得课程信息
        LearningLesson lesson = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();
        //3.封装返回
//        LearningLessonVO vo=new LearningLessonVO();
//        vo.setId(lesson.getId());
//        vo.setCourseId(lesson.getCourseId());
//        vo.setStatus(lesson.getStatus());
//        vo.setLearnedSections(lesson.getLearnedSections());
//        return null;
        return lesson;
    }

    /**
     * 统计课程的学习人数
     *
     * @param courseId
     * @return
     */
    @Override
    public Integer countLearingLessonByCourse(Long courseId) {
        Integer count = lambdaQuery().eq(LearningLesson::getCourseId, courseId)
                .count();
        return count;
    }

    @Override
    public void createPlan(LearningPlanDTO learningPlanDTO) {
        // 查询获得课表对象
        LearningLesson learningLesson = lambdaQuery().eq(LearningLesson::getUserId, UserContext.getUser())
                .eq(LearningLesson::getCourseId, learningPlanDTO.getCourseId())
                .one();
        // 健壮性判断
        if (learningLesson == null) {
            throw new BadRequestException("没有对应的课表对象");
        }
        // 更改学习计划频率 和状态
        learningLesson.setWeekFreq(learningPlanDTO.getFreq());
        learningLesson.setPlanStatus(PlanStatus.PLAN_RUNNING);
        //更新数据
        updateById(learningLesson);

    }

    /**
     * 定时任务 没隔15s查询课表课程是否过期 如果过期则修改课表状态
     */
    @Override
    public void springTaskExpireTime() {
        // 获得当初用户id
        Long userId = UserContext.getUser();

        // 根据userId获得课程没有过期的learninglesson集合
        List<LearningLesson> lessons = lambdaQuery()
                .eq(LearningLesson::getUserId, userId)
                .notIn(LearningLesson::getStatus,LessonStatus.EXPIRED)
                .list();

        // 将lessons转化为map集合 key：id，value：expireTime
        Map<Long, LocalDateTime> cmap = lessons.stream()
                .collect(Collectors.toMap(LearningLesson::getId, LearningLesson::getExpireTime));

        // 获得当前用户的所有课表id
        List<Long> ids = lessons.stream().map(LearningLesson::getId).collect(Collectors.toList());

        // 循环遍历lessons集合
        for (Long id : ids) {
            LocalDateTime expireTime = cmap.get(id);
            // 判断课程是否过期
            if (expireTime.isAfter(LocalDateTime.now())){
                return;
            }
            // 修改课程状态为已过期
            boolean update = lambdaUpdate().eq(LearningLesson::getId, id)
                    .set(LearningLesson::getStatus, LessonStatus.EXPIRED)
                    .update();
            if (!update){
                throw new DbException("修改课程状态为已过期失败");
            }

        }
        // 如果课程过期则将课表状态改为已过期
    }

    /**
     * 查询我本周的学习计划
     * @param pageQuery
     * @return
     */
    @Override
    public LearningPlanPageVO queryMyPlans(PageQuery pageQuery) {
        //1.获得用户Id
        Long userId = UserContext.getUser();
        LearningPlanPageVO learningPlanPageVO=new LearningPlanPageVO();
        //2.获得当前周的起始时间，获得本周已学完的章节 本周计划和完成总章节以后在统计
        LocalDateTime weekBeginTime = DateUtils.getWeekBeginTime(LocalDate.now());
        LocalDateTime weekEndTime = DateUtils.getWeekEndTime(LocalDate.now());


        //2.1先获得本周有学习计划的课程的id 学习中 未学习(已经排除了已过期和已学完) 有学习计划
        Page<LearningLesson> p = lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getPlanStatus, PlanStatus.PLAN_RUNNING)
                .in(LearningLesson::getStatus, LessonStatus.LEARNING, LessonStatus.NOT_BEGIN)
                .page(pageQuery.toMpPage("latest_learn_time", false));
        List<LearningLesson> records = p.getRecords();
        if (CollUtils.isEmpty(records)){
            return learningPlanPageVO;
        }
        List<Long> lessonIds = records.stream()
                .map(LearningLesson::getId).collect(Collectors.toList());

        // 根据有计划的课程的id获得学习记录
        QueryWrapper<LearningRecord> queryWrapper=new QueryWrapper<LearningRecord>();
        queryWrapper.eq("user_id", userId)
                .in("lesson_id", lessonIds)
                .eq("finished", true)
                .le("finished", weekEndTime)
                .ge("finished", weekBeginTime);
        List<LearningRecord> recordList = recordMapper.selectList(queryWrapper);

        // 本周总已学习章节数
        learningPlanPageVO.setWeekFinished(recordList.size());
        // 获得本周计划数的集合
        int countweek = records.stream()
                .map(LearningLesson::getWeekFreq).mapToInt(Integer::valueOf).sum();
        learningPlanPageVO.setWeekTotalPlan(countweek);
        // TODO 本周学习积分

        // 整理LearningPlanVo
        // 将learningLessons集合转成map集合 key是lessonId
        Map<Long, LearningLesson> cmap = records.stream()
                .collect(Collectors.toMap(LearningLesson::getId, c -> c));

        // 循环遍历 lessonIds  ***cmap集合是有计划没有计划都收集了 lessonIds 只收集了有计划的课表Id***
        List<LearningPlanVO> list=new ArrayList<LearningPlanVO>(lessonIds.size());
        for (Long lessonId :lessonIds){
            LearningLesson learningLesson = cmap.get(lessonId);
            // 拷贝相同成员变量名的
            LearningPlanVO learningPlanVO = BeanUtils.copyBean(learningLesson, LearningPlanVO.class);
            learningPlanVO.setId(lessonId);
            // 本周本课表已学习章节数
            Integer weekLearnedSections = (int) recordList.stream()
                    .filter(record -> record.getLessonId().equals(lessonId)).count();
            learningPlanVO.setWeekLearnedSections(weekLearnedSections);

            //根据lessonId获得courseId 进而获得courseName和sections
            LearningLesson lesson = getById(lessonId);
            CourseFullInfoDTO courseInfoById = courseClient
                    .getCourseInfoById(lesson.getCourseId(), false, false);
            // 拷贝courseName和sections
            learningPlanVO.setCourseName(courseInfoById.getName());
            learningPlanVO.setSections(courseInfoById.getSectionNum());

            // 添加对象到集合
            list.add(learningPlanVO);
        }
        //3.将查出的LearningPlanVO放入LearningPlanPageVO返回
        // total=lessonIds.size

        return learningPlanPageVO.pageInfo(p.getTotal(),p.getPages(),list);
    }

    /**
     * 监听订单退款成功，删除用户相关课程表
     */
    @Override
    public void deleteLessonByMq(OrderBasicDTO dto) {
        Long userId = dto.getUserId();
        List<Long> courseIds = dto.getCourseIds();
        remove(new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId,userId)
                .in(LearningLesson::getCourseId,courseIds)
        );
    }

    @Override
    public LearningLesson queryByUserAndCourseId(Long userId, Long courseId) {
        return getOne(buildUserIdAndCourseIdWrapper(userId, courseId));
    }

    private Wrapper<LearningLesson> buildUserIdAndCourseIdWrapper(Long userId, Long courseId) {
        LambdaQueryWrapper<LearningLesson> queryWrapper = new QueryWrapper<LearningLesson>()
                .lambda()
                .eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId);
        return queryWrapper;
    }

}
